package com.yjzx.util.common.execption;

import java.io.PrintStream;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.Set;

/**
 * @author yjzx
 * @date 2024/6/21
 * @description 可指定和修改输出的异常栈(允许没有异常栈)，只是修改异常栈可使用subStackTraceElement方法
 */
public class MyRuntimeException extends RuntimeException {
    private volatile StackTraceElement[] elements = null;


    public MyRuntimeException subStackTraceElement(int fromIndex) {
        return subStackTraceElement(fromIndex, Integer.MAX_VALUE);
    }

    public synchronized MyRuntimeException subStackTraceElement(int fromIndex, int toIndex) {
        this.elements = this.elements == null ? getStackTrace() : this.elements;
        StackTraceElement[] elements = subStackTraceElement(this.elements, fromIndex, toIndex);
        if (elements.length != 0) {
            this.elements = null;
            this.setStackTrace(elements);
        }
        return this;
    }

    public StackTraceElement[] subStackTraceElement(StackTraceElement[] elements, int fromIndex, int toIndex) {
        fromIndex = Math.max(Math.min(fromIndex, elements.length), 0);
        toIndex = Math.max(Math.min(toIndex, elements.length), 0);
        int size = toIndex - fromIndex;
        StackTraceElement[] stackTrace1 = new StackTraceElement[size];
        System.arraycopy(elements, fromIndex, stackTrace1, 0, size);
        return stackTrace1;
    }

    public MyRuntimeException() {
        super();
    }

    public MyRuntimeException(StackTraceElement[] elements) {
        super();
        this.elements = elements;
    }

    public MyRuntimeException(String message) {
        super(message);
    }

    public MyRuntimeException(String message, StackTraceElement[] elements) {
        super(message);
        this.elements = elements;
    }

    public MyRuntimeException(String message, Throwable cause) {
        super(message, cause);
    }

    public MyRuntimeException(String message, Throwable cause, StackTraceElement[] elements) {
        super(message, cause);
        this.elements = elements;
    }

    public MyRuntimeException(Throwable cause) {
        super(cause);
    }

    public MyRuntimeException(Throwable cause, StackTraceElement[] elements) {
        super(cause);
        this.elements = elements;
    }


    protected MyRuntimeException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }

    @Override
    public void printStackTrace(PrintStream s) {
        printStackTrace(new WrappedPrintStream(s));
    }

    @Override
    public void printStackTrace(PrintWriter s) {
        printStackTrace(new WrappedPrintWriter(s));
    }


    private void printStackTrace(PrintStreamOrWriter s) {
        // Guard against malicious overrides of Throwable.equals by
        // using a Set with identity equality semantics.
        Set<Throwable> dejaVu =
                Collections.newSetFromMap(new IdentityHashMap<Throwable, Boolean>());
        dejaVu.add(this);

        synchronized (s.lock()) {
            // Print our stack trace
            s.println(this);
            StackTraceElement[] trace = elements != null ? elements : getStackTrace();
            for (StackTraceElement traceElement : trace) {
                s.println("\tat " + traceElement);
            }

            Method method = null;
            for (Method method1 : Throwable.class.getMethods()) {
                if ("printEnclosedStackTrace".equals(method1.getName())) {
                    method = method1;
                    method.setAccessible(true);
                    break;
                }
            }
            try {
                // Print suppressed exceptions, if any
                for (Throwable se : getSuppressed()) {
                    method.invoke(s, trace, "Suppressed: ", "\t", dejaVu);
                }

                // Print cause, if any
                Throwable ourCause = getCause();
                if (ourCause != null) {
                    method.invoke(s, trace, "Caused by: ", "", dejaVu);
                }
            } catch (Exception e) {

            }

        }
    }

    private abstract static class PrintStreamOrWriter {

        abstract Object lock();


        abstract void println(Object o);
    }

    private static class WrappedPrintStream extends PrintStreamOrWriter {
        private final PrintStream printStream;

        WrappedPrintStream(PrintStream printStream) {
            this.printStream = printStream;
        }

        @Override
        Object lock() {
            return printStream;
        }

        @Override
        void println(Object o) {
            printStream.println(o);
        }
    }

    private static class WrappedPrintWriter extends PrintStreamOrWriter {
        private final PrintWriter printWriter;

        WrappedPrintWriter(PrintWriter printWriter) {
            this.printWriter = printWriter;
        }

        @Override
        Object lock() {
            return printWriter;
        }

        @Override
        void println(Object o) {
            printWriter.println(o);
        }
    }
}
